﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Web;
using System.Web.Hosting;
using UDS.Components;
using System.Data;

namespace UDS.Entity.DocumentFlow
{
    /// <summary>
    /// A style (表单) referenced by a document flow.
    /// </summary>
    public class FlowStyle
    {
        #region properties

        public int ID
        {
            get;
            set;
        }

        public string Name
        {
            get;
            set;
        }

        public string Description
        {
            get;
            set;
        }

        /// <summary>
        /// Get or set the name of the attached template file.
        /// </summary>
        public string Template
        {
            get;
            set;
        }

        /// <summary>
        /// Get the full virtual path of the template file.
        /// </summary>
        public string TemplateFullVirtualPath
        {
            get
            {
                if (string.IsNullOrEmpty(Template))
                {
                    return null;
                }

                if (ID <= 0)
                {
                    throw new InvalidOperationException("The id of the style must be greater than zero.");
                }

                string vDir = GetTemplateVDir(ID);
                string vPath = HostingEnvironment.VirtualPathProvider.CombineVirtualPaths(vDir, Template);

                // Verify the template file gets stored on the correct directory
                // to avoid potential security problems.
                if (0 != string.Compare(vPath, 0, TemplateVDir, 0, TemplateVDir.Length, StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new InvalidOperationException(string.Format("The template file '{0}' is invalid.", Template));
                }
                return vPath;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Get the template name of the given style.
        /// </summary>
        public static string GetStyleTemplate(int styleId)
        {
            FlowStyle s = GetFlowStyle(styleId);
            if (s != null)
            {
                return s.Template;
            }
            return null;
        }

        /// <summary>
        /// Get the information of the specified flow style.
        /// </summary>
        /// <param name="styleId">The id of the style.</param>
        /// <exception cref="ArgumentOutOfRangeException">
        /// The value of the parameter <paramref name="styleId"/> is equal to or smaller than zero.
        /// </exception>
        public static FlowStyle GetFlowStyle(int styleId)
        {
            if (styleId <= 0)
            {
                throw new ArgumentOutOfRangeException("styleId");
            }

            var db = new Database();
            var parameters = new[]
            {
                Database.MakeInParam("@StyleID", SqlDbType.Int, 4, styleId),
            };

            FlowStyle s = null;
            using (var dr = db.RunDataReader("dbo.sproc_Flow_GetStyle_V2", parameters))
            {
                if (dr.Read())
                {
                    s = ReadFlowStyle(dr);
                }
            }

            return s;
        }

        /// <summary>
        /// Get all styles defined in DB.
        /// </summary>
        public static IList<FlowStyle> GetAllFlowStyles()
        {
            List<FlowStyle> list = new List<FlowStyle>();
            var db = new Database();
            using (var dr = db.RunDataReader("dbo.sproc_Flow_GetStyle_V2"))
            {
                while (dr.Read())
                {
                    list.Add(ReadFlowStyle(dr));
                }
            }

            return list;
        }

        /// <summary>
        /// Create a new flow style
        /// </summary>
        public static FlowStyle AddStyle(string name, string description, HttpPostedFile templateFile)
        {
            FlowStyle s = new FlowStyle();
            s.Name = name;
            s.Description = description;

            // Get the name of the attached template file
            if (templateFile != null && templateFile.ContentLength > 0 && !string.IsNullOrEmpty(templateFile.FileName))
            {
                s.Template = Path.GetFileName(templateFile.FileName);
            }

            using (var db = new Database())
            {
                // Start a new transaction
                db.BeginTransaction();

                // Create a style in db
                var parameters = new[]
                {
                    Database.MakeInParam("@StyleName", SqlDbType.NVarChar, 64, s.Name),
                    Database.MakeInParam("@StyleRemark", SqlDbType.NVarChar, -1, s.Description),
                    Database.MakeInParam("@Teamplate", SqlDbType.NVarChar, 256, s.Template)
                };
                s.ID = db.RunProc("dbo.sproc_Flow_AddStyle", parameters);
                if (s.ID <= 0)
                {
                    throw new InvalidOperationException(string.Format("Failed to create a new style for '{0}'.", name));
                }

                if (!string.IsNullOrEmpty(s.Template))
                {
                    // Save the file to the local storage
                    s.SaveFile(templateFile);
                }

                // Commit the transaction then
                db.Commit();
            }

            return s;
        }

        /// <summary>
        /// Update the existing file.
        /// </summary>
        /// <param name="id">The style id.</param>
        /// <param name="name">The name of the style.</param>
        /// <param name="description">The description for the style.</param>
        /// <param name="templateFile">The new template file.</param>
        /// <param name="replaceOldTemplate">
        /// Specifies whether to delete the old template file if <see cref="templateFile"/> was not specified.
        /// </param>
        public static FlowStyle UpdateStyle(int id, string name, string description, HttpPostedFile templateFile, bool replaceOldTemplate)
        {
            if (id <= 0)
            {
                throw new ArgumentOutOfRangeException("id", "The style id must be greater than zero.");
            }

            FlowStyle s = new FlowStyle();
            s.ID = id;
            s.Name = name;
            s.Description = description;

            var parameters = new[]
            {
                Database.MakeInParam("@StyleID", SqlDbType.Int, 4, id),
                Database.MakeInParam("@StyleName", SqlDbType.NVarChar, 64, name),
                Database.MakeInParam("@StyleRemark", SqlDbType.NVarChar, -1, description),
                Database.MakeInParam("@Teamplate", SqlDbType.NVarChar, 256, null)
            };

            using (var db = new Database())
            {
                db.BeginTransaction();

                bool deleteOldFile = false;
                if (templateFile != null && templateFile.ContentLength > 0 && !string.IsNullOrEmpty(templateFile.FileName))
                {
                    deleteOldFile = true;
                    s.Template = Path.GetFileName(templateFile.FileName);
                }
                else
                {
                    deleteOldFile = replaceOldTemplate;
                    if (!deleteOldFile)
                    {
                        // Keep the old template file
                        FlowStyle oldStyle = GetFlowStyle(id);
                        if (oldStyle == null)
                        {
                            throw new InvalidOperationException(string.Format("The specified style '{0}' does not exist.", id));
                        }

                        s.Template = oldStyle.Template;
                    }
                }

                // Update the record in DB.
                db.RunProc(
                    "dbo.sproc_Flow_UpdateStyle_V2",
                    new[] {
                        Database.MakeInParam("@StyleID", SqlDbType.Int, 4, s.ID),
                        Database.MakeInParam("@StyleName", SqlDbType.NVarChar, 64, s.Name),
                        Database.MakeInParam("@StyleRemark", SqlDbType.NVarChar, -1, s.Description),
                        Database.MakeInParam("@Teamplate", SqlDbType.NVarChar, 256, s.Template),
                    });

                // Delete the old template file
                if (deleteOldFile)
                {
                    DeleteFlowStyleTemplate(db, id);
                }

                // Save the new template file
                s.SaveFile(templateFile);

                // Commit the transaction.
                db.Commit();
            }

            return s;
        }

        /// <summary>
        /// Delete the existing style.
        /// </summary>
        public static int DeleteStyle(int id)
        {
            if (id <= 0)
            {
                throw new ArgumentOutOfRangeException("id", "The style id must be greater than zero.");
            }

            var parameters = new[]
            {
                Database.MakeInParam("@StyleID", SqlDbType.Int, 4, id)
            };
            using (var db = new Database())
            {
                db.BeginTransaction();

                // Try to remove the record first
                // It will failed if the record is still referenced by other tables.
                var returnValue = db.RunProc("dbo.sproc_Flow_DeleteStyle_V2", parameters);

                // Then try to delete the existing template file.
                DeleteFlowStyleTemplate(db, id);

                db.Commit();
                return returnValue;
            }
        }

        private void SaveFile(HttpPostedFile templateFile)
        {
            Debug.Assert(templateFile != null);
            Debug.Assert(templateFile.ContentLength > 0);
            Debug.Assert(ID > 0);
            Debug.Assert(!string.IsNullOrEmpty(Template));

            var fullPath = HostingEnvironment.MapPath(TemplateFullVirtualPath);

            // Ensure that the directory exists
            var dir = new DirectoryInfo(Path.GetDirectoryName(fullPath));
            if (!dir.Exists)
            {
                dir.Create();
            }

            // Then save the file
            templateFile.SaveAs(fullPath);
        }

        #endregion

        #region Help methods

        /// <summary>
        /// Delete the existing template file for the specified style.
        /// </summary>
        private static void DeleteFlowStyleTemplate(Database db, int id)
        {
            if (id <= 0)
            {
                throw new ArgumentOutOfRangeException("id", "The flow style id must be greater than zero.");
            }

            FlowStyle style = GetFlowStyle(id);
            if (style != null && !string.IsNullOrEmpty(style.Template))
            {
                string fullVPath = style.TemplateFullVirtualPath;
                string fullPath = HostingEnvironment.MapPath(fullVPath);

                var fileInfo = new FileInfo(fullPath);
                if (fileInfo.Exists)
                {
                    fileInfo.Delete();
                }
            }
        }

        private static FlowStyle ReadFlowStyle(IDataReader dr)
        {
            var s = new FlowStyle();
            s.ID = (int)dr["Style_ID"];
            s.Name = Utility.ReadString(dr["Style_Name"]);
            s.Description = Utility.ReadString(dr["Style_Remark"]);
            s.Template = Utility.ReadString(dr["Template"]);

            return s;
        }

        #endregion

        #region Template Dir

        /// <summary>
        /// The virtual path to the template directory.
        /// </summary>
        private const string VDir = @"~/Files/DocumentFlow/Template/";
        private static string s_templateVDir;
        private static string s_templateDir;

        private static string TemplateVDir
        {
            get
            {
                if (string.IsNullOrEmpty(s_templateVDir))
                {
                    s_templateVDir = Utility.ResolvePath(VDir);
                }

                return s_templateVDir;
            }
        }

        /// <summary>
        /// Gets the path of the directory where the template files are stored.
        /// </summary>
        private static string TemplateDir
        {
            get
            {
                if (string.IsNullOrEmpty(s_templateDir))
                {
                    s_templateDir = HostingEnvironment.MapPath(TemplateVDir);
                }
                return s_templateDir;
            }
        }

        /// <summary>
        /// The path to the directory where the template files for a style are stored is:
        /// ~\Files\DocumentFlow\Template\[style id].
        /// </summary>
        private static string GetTemplateVDir(int id)
        {
            Debug.Assert(TemplateVDir != null || TemplateVDir.Length > 0);
            Debug.Assert(TemplateVDir[TemplateVDir.Length - 1] == '/');

            return string.Concat(TemplateVDir, id.ToString(), "/");
        }

        #endregion
    }
}
